package com.esun.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.esun.common.annotation.OptLog;
import com.esun.common.enums.LogAgent;
import com.esun.common.enums.LogInfo;
import com.esun.common.log.OperationLogEo;
import com.esun.common.mq.rabbitmq.producer.OperationLogProducer;
import com.esun.common.utils.IpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * @author John.xiao
 * @date 2021/10/11 14:35
 */
@Aspect
@Component
@Slf4j
@ConditionalOnExpression("${esun.log.operation.enable:false}")
public class OperationLogAspect {
	@Resource
	OperationLogProducer operationLogProducer;

	private final ThreadLocal<OperationLogEo> logThreadLocal = new ThreadLocal<>();

	@Pointcut("execution(public * com.esun.server.controller..*(..))")
	public void systemLog() {
	}
	@Value("${spring.application.name}")
	private String serviceName;

	@Around(value = "systemLog()")
	public Object getAndSendLog(ProceedingJoinPoint joinPoint)throws Throwable {
		ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
		MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
		Method targetMethod = methodSignature.getMethod();
		OperationLogEo logEo = new OperationLogEo();
		Object[] args = joinPoint.getArgs();
		List<Object> list = new ArrayList<>();
		if (args.length > 0) {
			for (Object object : args) {
				if (object instanceof MultipartFile) {
					JSONObject json = new JSONObject();
					json.put("fileName", ((MultipartFile) object).getName());
					list.add(json);
				} else {
					list.add(object);
				}
			}
			String param = JSONObject.toJSON(list).toString();
			logEo.setRequestParam(param);
		}
		String ip = IpUtil.getClientIp(request);
		logEo.setIp(ip);
		logEo.setMethod(targetMethod.getName());
		logEo.setRequestType(request.getMethod());
		logEo.setRequestUrl(request.getRequestURL().toString());
//		String userId = StpUtil.getLoginId().toString();
//		logEo.setUserId(userId);

		if (targetMethod.isAnnotationPresent(OptLog.class)) {
			OptLog optLog = targetMethod.getAnnotation(OptLog.class);
			logEo.setModule(optLog.module());
		}
		logEo.setService(serviceName);
		logThreadLocal.set(logEo);
		LogInfo logInfo = LogInfo.builder().build();
		BeanUtils.copyProperties(logEo, logInfo);
		LogAgent.set(logInfo);
		//1、记录执行时间
		long startTime = System.currentTimeMillis();
		Object result = joinPoint.proceed(joinPoint.getArgs());
		long endTime = System.currentTimeMillis();
		long totalTime = endTime - startTime;

		logEo.setCostTime(totalTime);
		logThreadLocal.set(logEo);

		try {
			operationLogProducer.sendLog(logEo);
		} catch (Exception exception) {
			log.error("保存操作日志出错：", exception);
		} finally {
			logThreadLocal.remove();
		}
		return result;
	}


	@AfterThrowing(pointcut = "systemLog()", throwing = "e")
	public void handle(JoinPoint point, Exception e) {
		String exception = stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace());
		OperationLogEo logEo = logThreadLocal.get();
		if (logEo != null) {
			int len = 2000;
			if (StringUtils.isNotEmpty(exception) && exception.length() > len) {
				exception = exception.substring(0, len);
			}
			logEo.setException(exception);
			try {
				operationLogProducer.sendLog(logEo);
			} catch (Exception ex) {
				log.error("保存操作日志出错：", ex);
			} finally {
				logThreadLocal.remove();
			}
		}
	}

	/**
	 * 转换异常信息为字符串
	 *
	 * @param exceptionName    异常名称
	 * @param exceptionMessage 异常信息
	 * @param elements         堆栈信息
	 */
	public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
		StringBuilder strbuff = new StringBuilder();
		for (StackTraceElement stet : elements) {
			strbuff.append(stet).append("\n");
		}
		return exceptionName + ":" + exceptionMessage + "\n\t" + strbuff;
	}
}
